package com.lin.subject.infra.basic.service.impl;

import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import com.lin.subject.commom.entity.PageResult;
import com.lin.subject.commom.enums.SubjectInfoTypeEnum;
import com.lin.subject.infra.basic.entity.EsSubjectFields;
import com.lin.subject.infra.basic.entity.SubjectInfoEs;
import com.lin.subject.infra.basic.es.EsSourceData;
import com.lin.subject.infra.basic.es.EsUtil;
import com.lin.subject.infra.basic.service.SubjectEsService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Service
@Slf4j
public class SubjectEsServiceImpl implements SubjectEsService {

    @Resource
    private EsUtil esUtil;

    /**
     * 目标索引
     */
    private final String indexName = "subject_index";

    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        Map<String, Object> data = convert2EsSourceData(subjectInfoEs);
        esSourceData.setDocId(subjectInfoEs.getDocId().toString());
        esSourceData.setData(data);

        String orUpdate = esUtil.doc.createOrUpdate(indexName, subjectInfoEs.getDocId(), esSourceData);
        return true;
    }

    private Map<String, Object> convert2EsSourceData(SubjectInfoEs subjectInfoEs) {
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectFields.SUBJECT_ID, subjectInfoEs.getSubjectId());
        data.put(EsSubjectFields.DOC_ID, subjectInfoEs.getDocId());
        data.put(EsSubjectFields.SUBJECT_NAME, subjectInfoEs.getSubjectName());
        data.put(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer());
        data.put(EsSubjectFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType());
        data.put(EsSubjectFields.CREATE_USER, subjectInfoEs.getCreateUser());
        data.put(EsSubjectFields.CREATE_TIME, subjectInfoEs.getCreateTime());
        return data;
    }

    /**
     * 高亮全文搜索
     *
     * @param req
     * @return
     */
    @Override
    public PageResult<SubjectInfoEs> querySubjectList(SubjectInfoEs req) {
        PageResult<SubjectInfoEs> pageResult = new PageResult<>();
        SearchResponse searchResponse = createSearchListQuery(req);

        List<SubjectInfoEs> subjectInfoEsList = new LinkedList<>();

        // 查询结果
        HitsMetadata subjectHits = searchResponse.hits();

        pageResult.setPageNo(req.getPageNo());
        pageResult.setPageSize(req.getPageSize());

        if (Objects.equals(subjectHits, null) || Objects.equals(subjectHits.hits(), null)) {
            pageResult.setRecords(subjectInfoEsList);
            pageResult.setTotal(0);
            return pageResult;
        }

        List<Hit<SubjectInfoEs>> hits = subjectHits.hits();

        // 流式遍历查询结果：用高亮字段替换原文档字段
        hits.stream().forEach(hit -> {
            SubjectInfoEs doc = convertResult(hit);
            if (Objects.nonNull(doc)) {
                // 原文档转为VO，加入VO对象集合中
                subjectInfoEsList.add(doc);
            }
        });
        pageResult.setRecords(subjectInfoEsList);
        long total = subjectHits.total().value();
        pageResult.setTotal(Long.valueOf(total).intValue());
        return pageResult;
    }


    private SubjectInfoEs convertResult(Hit<SubjectInfoEs> hit) {
        SubjectInfoEs result = hit.source();


        result.setScore(new BigDecimal(String.valueOf(hit.score())).multiply(new BigDecimal("100.00")
                .setScale(2, RoundingMode.HALF_UP)));

        //处理name的高亮
        Map<String, List<String>> highlightFields = hit.highlight();

        // 高亮标题字段
        List<String> sujectNames = highlightFields.get(EsSubjectFields.SUBJECT_NAME);
        // 把它拼成一段话
        if (Objects.nonNull(sujectNames) && !sujectNames.isEmpty()) {
            StringBuilder stringBuilder = new StringBuilder();
            for (String string : sujectNames) {
                stringBuilder.append(string);
            }
            result.setSubjectName(stringBuilder.toString());
        }

        // 高亮内容字段
        List<String> subjectAnswers = highlightFields.get(EsSubjectFields.SUBJECT_ANSWER);
        // 把它拼成一段话
        if (Objects.nonNull(subjectAnswers) && !subjectAnswers.isEmpty()) {
            StringBuilder stringBuilder = new StringBuilder();
            for (String string : subjectAnswers) {
                stringBuilder.append(string);
            }
            result.setSubjectAnswer(stringBuilder.toString());
        }

        return result;
    }


    @SneakyThrows
    private SearchResponse createSearchListQuery(SubjectInfoEs req) {
        String iName = indexName.toLowerCase(Locale.ROOT);

        String kw = req.getKeyWord().trim();

        Query nameQuery = MultiMatchQuery.of(m -> m
                .fields(EsSubjectFields.SUBJECT_NAME)
                .query(kw)
                .minimumShouldMatch("70%")
        )._toQuery();

        Query answerQuery = MultiMatchQuery.of(m -> m
                .fields(EsSubjectFields.SUBJECT_ANSWER)
                .query(kw).minimumShouldMatch("30%")
        )._toQuery();

        Query typeQuery = MultiMatchQuery.of(m -> m
                .fields(EsSubjectFields.SUBJECT_TYPE)
                .query(String.valueOf(SubjectInfoTypeEnum.BRIEF.getCode()))
        )._toQuery();

        // 起始文档值（从0开始）
        Integer from = (req.getPageNo() - 1) * req.getPageSize();

        // 存放高亮的字段，默认与文档字段一致
        HighlightField hf = new HighlightField.Builder().build();

        Highlight highlight = new Highlight.Builder()
                // 前后缀默认就是em，可省略
                .preTags("<span style=\"color:red\">")
                .postTags("</span>")
                .fields(EsSubjectFields.SUBJECT_NAME, hf)
                .fields(EsSubjectFields.SUBJECT_ANSWER, hf)
                .requireFieldMatch(false)
                .build();


        // 异步
        SearchResponse<SubjectInfoEs> response = esUtil.getEsAsyncClient().search(s -> s
                        .index(iName)
                        .query(q -> q
                                // boolean 嵌套搜索；must需同时满足，should一个满足即可
                                .bool(b -> b
                                        .should(nameQuery)
                                        .should(answerQuery)
                                )
                        )
                        .highlight(highlight)
                        .from(from).size(req.getPageSize()),
                SubjectInfoEs.class
        ).get();

//        .query(q -> q
//                // 布尔比较：有一个条件满足即可
//                .bool(b -> b
//                        // 条件一：must：两个子条件都满足时，条件才成立；【公共文档】
//                        .should(sq1 -> sq1.bool(sqb1 -> sqb1.must(byType1, byKeyword)))
//                        // 条件二：must：三个子条件都满足时，条件才成立；【私有文档】
//                        .should(sq2 -> sq2.bool(sqb2 -> sqb2.must(byType2, byOwner, byKeyword)))
//                )
//        ).highlight(highlight)
        return response;
    }

}
